#!/usr/bin/python
#
## License:
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

## Help Info:
# This is a simple script to package VM's into portable appliances
# for the purpose of backing them up.  It will shut down running appliances
# start them back up again if they are running.  It is meant to run as a cronjob
# so all configuration is stored in a set of global variables below the imports.
# Due to this, there's no 'help' function or options handler, or even any arguements

# Reminder: Cron runs in a reduced shell, please provide full paths for all requested information

# It depends upon the vboxtool script, a version patched for 4.x can be found at
# http://code.google.com/p/pni-toolbox/source/browse/trunk/vboxtool-branch/script/vboxtool

## imports
import os.path
import subprocess
import logging
from datetime import datetime
from time import sleep
from sys import exit

## config
#path to vboxtool
#as direction of this script has changed somewhat, both paths are now required
#this is due to some features not being exposed by vboxtool in a manner that 
#was useful from the standpoint of this script.  Specifically the ability 
#to force a save state and restore from it is lacking.
pathVBOXTool = "/usr/local/bin/vboxtool"
pathVBOXMANAGE = "/usr/bin/vboxmanage"

pathBackup = "/mnt/vmware-backup/87prospect/"

#array of VM's to backup
listVMs = ["sketchbook", "DNS", "nagios87prospectminion", "loghost2"]

#configure feedback level
loggerLevel=logging.INFO #valid levels: DEBUG,INFO,WARNING,ERROR,CRITICAL ##prepend 'logging.'

## helper functions
def getRunningVMList():
	logging.debug("starting vboxtool showrun check")

	vboxtoolCheck = subprocess.Popen([pathVBOXTool, "showrun"] , stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
	logging.debug("Vboxtool showrun: \n" + vboxtoolCheck)
	
	vboxtoolCheck = vboxtoolCheck.strip()
	
	vboxtoolCheck = vboxtoolCheck.split('\n')
	
	logging.debug("Number of Running VMs: " + str(len(vboxtoolCheck)))
	
	runningVMs=[]
	
	for VM in vboxtoolCheck:
		VM = VM.split(':')
		runningVMs.append(VM[0])
	
	logging.debug("Running VMs: " + str(runningVMs))
	
	logging.debug("Running VM List Retrieved")
	
	
	
	return runningVMs

def stopVM(VM):
	savestate=subprocess.Popen([pathVBOXMANAGE, "controlvm", str(VM), "savestate"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	
	savestateOutput = savestate.communicate()
	
	logging.debug(str(VM) + " Save State Output: \n" + "stdout: \n" + savestateOutput[0] + "\n stderr: \n" + savestateOutput[1])

	return 0	

def startVM(VM):
	startvm=subprocess.Popen([pathVBOXMANAGE, "startvm", str(VM), "--type", "headless"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

	startvmOutput = startvm.communicate()

	logging.debug(str(VM) + " Restart Output: \n" + "stdout: \n" + startvmOutput[0] + "\n stderr: \n" + startvmOutput[1])
	return 0

def backupVM(VM):

	#if a previous backup exists move it asside ## current work, work around backup already existing issue.
	#if os.path.exists(pathBackup+str(VM)+"-backup.ovf"):
		

	#this function uses the VM MANAGER export command for backups.
	backupvm=subprocess.Popen([pathVBOXMANAGE, "export", str(VM), "-o", pathBackup + str(VM) + "-" + str(datetime.today().month) + "-" + str(datetime.today().day) +".ovf"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	
	backupvmOutput = backupvm.communicate()
	
	logging.info(str(VM) + " Backup Output: \n" + "stdout: \n" + backupvmOutput[0] + "\n stderr: \n" + backupvmOutput[1])
	return 0

def sanityChecks():
	
	check = True
	if not os.path.exists(pathVBOXTool):
		logging.error("vboxtool Not Found")
		check = False


	if not os.path.exists(pathVBOXMANAGE):
		logging.error("VBoxManage Not Found")
		check = False

			
	if not os.path.exists(pathBackup):
		logging.error("Backup Directory Not Found")
		check = False	

	if not check:
		return check

	#note: this sanity check is insanely inefficient.  However this isn't exactly the kind of thing you run
	#from the command line so it should be transparent in the grand scheme of things.
		
	vboxtoolCheck = subprocess.Popen([pathVBOXTool, "show"] , stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
	vboxtoolCheck = vboxtoolCheck.strip()
	vboxtoolCheck = vboxtoolCheck.split('\n')
	
	VMs=[]
	for VM in vboxtoolCheck:
		VM = VM.split(':')
		#VM = VM[0].strip("\"")
		VMs.append(VM[0])
	
	for VM in listVMs:
		if VM not in VMs:
			logging.error("Virtual Machine " + str(VM) + " Not Found")
			check = False

	return check

## main
def main():

	logger = logging.getLogger()
	logger.setLevel(loggerLevel)

	if not sanityChecks():
		logging.error("Sanity Checks Failed.  Quiting")
		return -1
	logging.debug("Sanity Checks Passed")
	logging.info("Getting List of Running VMs")
	runningVMs = getRunningVMList()

	logger.info("Beginning Backup of VMs")	
	
	for VM in listVMs:
		if VM in runningVMs:
			logging.info("Saving State of " + str(VM))
			stopVM(VM)
			
			sleep(1)
			
			logging.info("Backing up " + str(VM))
			backupVM(VM)
			
			sleep(1) #appears to be some timing errors when backup errors out.
			
			logging.info("Restoring " + str(VM) + " from Saved State")
			startVM(VM)

		else:
			logging.info("Backing up " + str(VM))
			backupVM(VM)
	
	logging.info("Backups Complete")

	return 0
    
if __name__ == '__main__':
	exit(main())
	
	
# Copyright (c) <2011> <Garrett McGrath>

 # Permission is hereby granted, free of charge, to any person
 # obtaining a copy of this software and associated documentation
 # files (the "Software"), to deal in the Software without
 # restriction, including without limitation the rights to use,
 # copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the
 # Software is furnished to do so, subject to the following
 # conditions:

 # The above copyright notice and this permission notice shall be
 # included in all copies or substantial portions of the Software.

 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 # OTHER DEALINGS IN THE SOFTWARE.
